#include "asteroids.h"
#include <cassert>

/***************************************************************
 * Asteroids constructor
 ***************************************************************/
Asteroids::Asteroids()
{
   for (int i = 0; i < MAX_BIG_ROCKS; i++)
      m_rocks.push_back(new BigRock());

   int initXpos = -210;
   int initYpos =  180;

   for (int iLife  = 0; iLife < MAX_LIVES; iLife++)
   {
      m_extraLives[iLife].setX(initXpos += 15);
      m_extraLives[iLife].setY(initYpos);
   }
}

/***************************************************************
 * Operator ++ : Moves the game forward
 ***************************************************************/
void Asteroids::operator ++ (int postfix)
{
   if (m_ship.isAlive())
   {
      m_ship++;
      for (int i = 0; i < MAX_BULLETS; i++)
         if (m_bullets[i].isFired())
         {
            m_bullets[i]++;
            if (m_bullets[i].getCountdown() == 0)
              m_bullets[i] = Bullet();

            m_bullets[i].subtractFromCountdown();
         }
   }
   list <Rock *>::iterator it;
   for (it = m_rocks.begin(); it != m_rocks.end(); it++)
      (*(*it))++;
}

/***************************************************************
 * Object Hit : Checs to see if any of the object in the game
 *              have been hit
 *              NOTE: We knon that there is duplicate code but when
 *                    we tried to make a list of moveables and
 *                    tried to to make a generic hit detection
 *                    function but the game would crash and burn
 *                    so we decided to make two seperate hit
 *                    detection functions.
 ***************************************************************/
void Asteroids::objectHit()
{
   list <Rock *>::iterator it;
   for (it = m_rocks.begin(); it != m_rocks.end(); it++)
      for (int iBullet = 0; iBullet < MAX_BULLETS; iBullet++)
         if (m_bullets[iBullet].isFired())
            if ( (minDist (it, iBullet) < (*it)->getRadius()))
               switch ((*it)->getSize())
               {
                  case BIG:

                     // adds to the score
                     m_score.addScore(BIG_ROCK_POINT_VALUE);

                     // create the new rocks when this rock was hit
                     m_rocks.push_back(new MediumRock(it, LEFT,  0.4));
                     m_rocks.push_back(new MediumRock(it, RIGHT, 0.4));
                     m_rocks.push_back(new SmallRock (it, UP,    0.6));

                     // erases the rock that was hit
                     it = m_rocks.erase(it);

                     // resets the bulet that hitthe rock
                     m_bullets[iBullet] = Bullet();
                     break;

                  case MEDIUM:
                     m_score.addScore(MED_ROCK_POINT_VALUE);
                     m_rocks.push_back(new SmallRock(it, UP,   0.6));
                     m_rocks.push_back(new SmallRock(it, DOWN, 0.6));
                     it = m_rocks.erase(it);
                     m_bullets[iBullet] = Bullet();
                     break;

                  case SMALL:
                     m_score.addScore(SML_ROCK_POINT_VALUE);
                     it = m_rocks.erase(it);
                     m_bullets[iBullet] = Bullet();
                     break;
               }

   for (it = m_rocks.begin(); it != m_rocks.end(); it++)
      if (minDistShip(it) < (*it)->getRadius())
         switch ((*it)->getSize())
         {
            case BIG:

               // create the new rocks when this rock was hit
               m_rocks.push_back(new MediumRock(it, LEFT,  0.4));
               m_rocks.push_back(new MediumRock(it, RIGHT, 0.4));
               m_rocks.push_back(new SmallRock (it, UP,    0.6));

               // remove the rock that was hit
               it = m_rocks.erase(it);

               // Makes it so one of the drawn lives areremoved
               m_extraLives[m_ship.getNumLives() - 2].setIsAlive(false);

               // remove one of the ships lives
               m_ship.looseLife();

               // when the ships lives are gone, end the game
               if (m_ship.getNumLives() == 0)
                  gameOver();

               // if the ship was hit, put it back int the start location
               m_ship.resetShip();
               break;

            case MEDIUM:
               m_rocks.push_back(new SmallRock(it, UP,   0.6));
               m_rocks.push_back(new SmallRock(it, DOWN, 0.6));
               it = m_rocks.erase(it);
               m_extraLives[m_ship.getNumLives() - 2].setIsAlive(false);
               m_ship.looseLife();
               if (m_ship.getNumLives() == 0)
                  gameOver();
               m_ship.resetShip();
               break;

            case SMALL:
               it = m_rocks.erase(it);
               m_extraLives[m_ship.getNumLives() - 2].setIsAlive(false);
               m_ship.looseLife();
               if (m_ship.getNumLives() == 0)
                  gameOver();
               m_ship.resetShip();
               break;
               }
}

/***************************************************************
 * Game Over
 ***************************************************************/
void Asteroids::gameOver()
{
   cout << "\t\t\t\tGame Over!" << endl;
   m_ship.setIsAlive(false);
}

/***************************************************************
 * Draw Objects
 ***************************************************************/
void Asteroids::draw()
{
   m_score.drawScore();

   if (m_ship.isAlive())
   {
      m_ship.draw();
      for (int i = 0; i < MAX_BULLETS; i++)
         if (m_bullets[i].isFired())
            m_bullets[i].draw();
   }

   for (int iShip = 0; iShip < MAX_LIVES - 1; iShip++)
      if(m_extraLives[iShip].isAlive())
         m_extraLives[iShip].draw();

   list <Rock *>::iterator it;
   for (it = m_rocks.begin(); it != m_rocks.end(); it++)
      (*it)->draw();
}

/***************************************************************
 * Interact
 * Takes the users input and determines what to do with it.
 ***************************************************************/
void Asteroids::interact(const Interface *pUI)
{
   // Tells when the 'Up', 'Left', 'Right' arrow keys are pressed
   m_ship.event(pUI->isUp(), pUI->isLeft(), pUI->isRight());

   if (pUI->isSpace() || pUI->isF1())
      fire();
}

/**********************************************************
 * fire : fires bullets
 **********************************************************/
void Asteroids::fire()
{
   // we only want to have this function used five times,
   // starting at zero uses
   static int iBullet = 0;

   for (int i = 0; i < MAX_BULLETS; i++)
   {
      m_bullets[iBullet].setWrap (true);
      m_bullets[iBullet].setFired(true);
      m_bullets[iBullet].setPoint(m_ship.getPoint());
      // give that bullet a magnitude
      m_bullets[iBullet].setDx((-sin(deg2rad(m_ship.getAngle())) * 5) + m_ship.getDx());
      m_bullets[iBullet].setDy(( cos(deg2rad(m_ship.getAngle())) * 5) + m_ship.getDy());
   }
   // if the function has been called MAX_BULLET times, reset to zero
   if (++iBullet == MAX_BULLETS)
      iBullet = 0;
}

/***************************************************************
 * Get Max Value
 * Returnes the greatest value of the bird and the bullets.
 ***************************************************************/
float Asteroids::getMaxValue(list <Rock *>::iterator &iRock, int &iBullet)
{
   float max = 0;
   // finds the highest value out of all the bullets and birds
   // values
   max = (m_bullets[iBullet].getDx() > m_bullets[iBullet].getDy()) ? m_bullets[iBullet].getDx() : m_bullets[iBullet].getDy();

   max = (max > (*iRock)->getX()) ? max : (*iRock)->getX();
   max = (max > (*iRock)->getY()) ? max : (*iRock)->getY();

   return max;
}

/***************************************************************
 * Min Distance
 ***************************************************************/
float Asteroids::minDist(list <Rock *>::iterator &iRock, int &iBullet)
{
   float max = getMaxValue(iRock, iBullet); // set the max value
   float min = (*iRock)->getRadius() * (*iRock)->getRadius();
   float distanceSquared;

   for (int i = 0; i < max; i++)
   {
      distanceSquared = (((m_bullets[iBullet].getX() + m_bullets[iBullet].getDx() * i / max) -
                            ((*iRock)->getX() + (*iRock)->getDx() * i / max))
                            *
                            ((m_bullets[iBullet].getX() + m_bullets[iBullet].getDx() * i / max) -
                            ((*iRock)->getX() + (*iRock)->getDx() * i / max)))
                           +
                           (((m_bullets[iBullet].getY() + m_bullets[iBullet].getDy() * i / max) -
                            ((*iRock)->getY() + (*iRock)->getDy() * i / max))
                            *
                            ((m_bullets[iBullet].getY() + m_bullets[iBullet].getDy() * i / max) -
                            ((*iRock)->getY() + (*iRock)->getDy() * i / max)));

      min = (distanceSquared < min) ? distanceSquared : min;
   }
   return sqrt(min); // returns the smallest distance
}

/***************************************************************
 * Get Max Value
 * Returnes the greatest value of the bird and the bullets.
 ***************************************************************/
float Asteroids::getMaxValueShip(list <Rock *>::iterator &iRock)
{
   float max = 0;

   max = (m_ship.getDx() > m_ship.getDy()) ? m_ship.getDx() : m_ship.getDy();
   max = (max > (*iRock)->getX()) ? max : (*iRock)->getX();
   max = (max > (*iRock)->getY()) ? max : (*iRock)->getY();

   return max;
}

/***************************************************************
 * Min Distance
 ***************************************************************/
float Asteroids::minDistShip(list <Rock *>::iterator &iRock)
{
   float max = getMaxValueShip(iRock); // set the max value
   float min = (*iRock)->getRadius() * (*iRock)->getRadius() + 25;
   float distanceSquared;
   for (int i = 0; i < max; i++)
   {
      distanceSquared = (((m_ship.getX() + m_ship.getDx() * i / max) -
                         ((*iRock)->getX() + (*iRock)->getDx() * i / max))
                         *
                         ((m_ship.getX() + m_ship.getDx() * i / max) -
                         ((*iRock)->getX() + (*iRock)->getDx() * i / max)))
                        +
                         (((m_ship.getY() + m_ship.getDy() * i / max) -
                          ((*iRock)->getY() + (*iRock)->getDy() * i / max))
                         *
                          ((m_ship.getY() + m_ship.getDy() * i / max) -
                          ((*iRock)->getY() + (*iRock)->getDy() * i / max)));

      min = (distanceSquared < min) ? distanceSquared : min;
   }

   return sqrt(min); // returns the smallest distance
}
